#include "CSceneNodeAnimatorCameraFPS.h"
#include "CCursorControl.h"
#include "CRenderDriverD3D11.h"
#include "CSceneManager.h"
#include "IEventReceiver.h"


ldx::scene::CSceneNodeAnimatorCameraFPS::CSceneNodeAnimatorCameraFPS(
	device::CCursorControl * cursorControl, float rotateSpeed,
	float moveSpeed, float jumpSpeed, SKeyMap * keyMapArray, 
	uint keyMapSize, bool noVerticalMovement, bool invertY)
		:CursorControl(cursorControl), MaxVerticalAngle(88.0f),
		MoveSpeed(moveSpeed), RotateSpeed(rotateSpeed), JumpSpeed(jumpSpeed),
		MouseYDirection(invertY ? -1.0f : 1.0f),
		LastAnimationTime(0), firstUpdate(true), firstInput(true), NoVerticalMovement(noVerticalMovement)
{
#ifdef _DEBUG
	setDebugName("CCameraSceneNodeAnimatorFPS");
#endif

	if (CursorControl)
		CursorControl->grab();

	allKeysUp();

	// create key map
	if (!keyMapArray || !keyMapSize)
	{
		// create default key map
		KeyMap.push_back(SKeyMap(EKA_MOVE_FORWARD, ldx::KEY_UP));
		KeyMap.push_back(SKeyMap(EKA_MOVE_BACKWARD, ldx::KEY_DOWN));
		KeyMap.push_back(SKeyMap(EKA_STRAFE_LEFT, ldx::KEY_LEFT));
		KeyMap.push_back(SKeyMap(EKA_STRAFE_RIGHT, ldx::KEY_RIGHT));
		KeyMap.push_back(SKeyMap(EKA_JUMP_UP, ldx::KEY_KEY_J));
	}
	else
	{
		// create custom key map
		setKeyMap(keyMapArray, keyMapSize);
	}
}

ldx::scene::CSceneNodeAnimatorCameraFPS::~CSceneNodeAnimatorCameraFPS()
{
	if (CursorControl)
		CursorControl->drop();
}

void ldx::scene::CSceneNodeAnimatorCameraFPS::animateNode(ISceneNode * node, float tm)
{
	uint timeMs = (uint)(1000.f * tm);
	if (!node || node->getType() != ESNT_CAMERA)
		return;

	ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node);

	if (firstUpdate)
	{
		camera->updateAbsolutePosition();
		if (CursorControl)
		{
			CursorControl->setPosition(0.5f, 0.5f);
			CursorPos = CenterCursor = CursorControl->getRelativePosition();
		}

		LastAnimationTime = timeMs;

		firstUpdate = false;
	}

	// If the camera isn't the active camera, and receiving input, then don't process it.
	if (!camera->isInputReceiverEnabled())
	{
		firstInput = true;
		return;
	}

	if (firstInput)
	{
		allKeysUp();
		firstInput = false;
	}

	scene::CSceneManager * smgr = camera->getSceneManager();
	if (smgr && smgr->getActiveCamera() != camera)
		return;

	// get time
	float timeDiff = (float)(timeMs - LastAnimationTime);
	LastAnimationTime = timeMs;

	// update position
	mt::vec3f pos = camera->getPosition();

	// Update rotation
	mt::vec3f target = (camera->getTarget() - camera->getAbsolutePosition());
	mt::vec3f relativeRotation = target.getHorizontalAngle();

	if (CursorControl)
	{
		if (CursorPos != CenterCursor)
		{
			relativeRotation.Y -= (0.5f - CursorPos.X) * RotateSpeed;
			relativeRotation.X -= (0.5f - CursorPos.Y) * RotateSpeed * MouseYDirection;

			// X < MaxVerticalAngle or X > 360-MaxVerticalAngle

			if (relativeRotation.X > MaxVerticalAngle * 2 &&
				relativeRotation.X < 360.0f - MaxVerticalAngle)
			{
				relativeRotation.X = 360.0f - MaxVerticalAngle;
			}
			else
				if (relativeRotation.X > MaxVerticalAngle &&
					relativeRotation.X < 360.0f - MaxVerticalAngle)
				{
					relativeRotation.X = MaxVerticalAngle;
				}

			// Do the fix as normal, special case below
			// reset cursor position to the centre of the window.
			CursorControl->setPosition(0.5f, 0.5f);
			CenterCursor = CursorControl->getRelativePosition();

			// needed to avoid problems when the event receiver is disabled
			CursorPos = CenterCursor;
		}

		// Special case, mouse is whipped outside of window before it can update.
		render::IRenderDriver* driver = smgr->getRenderDriver();
		mt::vector2d<uint> mousepos(uint(CursorControl->getPosition().X), uint(CursorControl->getPosition().Y));
		mt::rect<uint> screenRect(0, 0, driver->getScreenSize().X, driver->getScreenSize().Y);

		// Only if we are moving outside quickly.
		bool reset = !screenRect.isPointInside(mousepos);

		if (reset)
		{
			// Force a reset.
			CursorControl->setPosition(0.5f, 0.5f);
			CenterCursor = CursorControl->getRelativePosition();
			CursorPos = CenterCursor;
		}
	}

	// set target

	target.set(0, 0, mt::MAX(1.f, pos.getLength()));
	mt::vec3f movedir = target;

	mt::mat4 mat;
	mat.setRotationDegrees(mt::vec3f(relativeRotation.X, relativeRotation.Y, 0));
	mat.transformVec(target);

	if (NoVerticalMovement)
	{
		mat.setRotationDegrees(mt::vec3f(0, relativeRotation.Y, 0));
		mat.transformVec(movedir);
	}
	else
	{
		movedir = target;
	}

	movedir.normalize();

	if (CursorKeys[EKA_MOVE_FORWARD])
		pos += movedir * timeDiff * MoveSpeed;

	if (CursorKeys[EKA_MOVE_BACKWARD])
		pos -= movedir * timeDiff * MoveSpeed;

	// strafing

	mt::vec3f strafevect = target;
	strafevect = strafevect.crossProduct(camera->getUpVector());

	if (NoVerticalMovement)
		strafevect.Y = 0.0f;

	strafevect.normalize();

	if (CursorKeys[EKA_STRAFE_LEFT])
		pos += strafevect * timeDiff * MoveSpeed;

	if (CursorKeys[EKA_STRAFE_RIGHT])
		pos -= strafevect * timeDiff * MoveSpeed;

	// For jumping, we find the collision response animator attached to our camera
	// and if it's not falling, we tell it to jump.
	/*if (CursorKeys[EKA_JUMP_UP])
	{
		const ISceneNodeAnimatorList& animators = camera->getAnimators();
		ISceneNodeAnimatorList::const_iterator it = animators.begin();
		while (it != animators.end())
		{
			if (ESNAT_COLLISION_RESPONSE == (*it)->getType())
			{
				ISceneNodeAnimatorCollisionResponse * collisionResponse =
					static_cast<ISceneNodeAnimatorCollisionResponse *>(*it);

				if (!collisionResponse->isFalling())
					collisionResponse->jump(JumpSpeed);
			}

			it++;
		}
	}*/

	// write translation
	camera->setPosition(pos);

	// write right target
	target += pos;
	camera->setTarget(target);
}

bool ldx::scene::CSceneNodeAnimatorCameraFPS::OnEvent(const SEvent & evt)
{
	switch (evt.EventType)
	{
	case EET_KEY_INPUT_EVENT:
		for (uint i = 0; i<KeyMap.size(); ++i)
		{
			if (KeyMap[i].KeyCode == evt.KeyInput.Key)
			{
				CursorKeys[KeyMap[i].Action] = evt.KeyInput.PressedDown;
				return true;
			}
		}
		break;

	case EET_MOUSE_INPUT_EVENT:
		if (evt.MouseInput.Event == EMIE_MOUSE_MOVED)
		{
			CursorPos = CursorControl->getRelativePosition();
			return true;
		}
		break;

	default:
		break;
	}

	return false;
}

float ldx::scene::CSceneNodeAnimatorCameraFPS::getMoveSpeed() const
{
	return  MoveSpeed;
}

void ldx::scene::CSceneNodeAnimatorCameraFPS::setMoveSpeed(float moveSpeed)
{
	MoveSpeed = moveSpeed;
}

float ldx::scene::CSceneNodeAnimatorCameraFPS::getRotateSpeed() const
{
	return RotateSpeed;
}

void ldx::scene::CSceneNodeAnimatorCameraFPS::setRotateSpeed(float rotateSpeed)
{
	RotateSpeed = rotateSpeed;
}

void ldx::scene::CSceneNodeAnimatorCameraFPS::setKeyMap(SKeyMap * map, uint count)
{
	// clear the keymap
	KeyMap.clear();

	// add actions
	for (uint i = 0; i<count; ++i)
	{
		KeyMap.push_back(map[i]);
	}
}

void ldx::scene::CSceneNodeAnimatorCameraFPS::setKeyMap(const std::vector<SKeyMap>& keymap)
{
	KeyMap.clear();
	KeyMap = keymap;
}

const std::vector<ldx::SKeyMap>& ldx::scene::CSceneNodeAnimatorCameraFPS::getKeyMap() const
{
	return KeyMap;
}

void ldx::scene::CSceneNodeAnimatorCameraFPS::setVerticalMovement(bool allow)
{
	NoVerticalMovement = !allow;
}

void ldx::scene::CSceneNodeAnimatorCameraFPS::setInvertMouse(bool invert)
{
	if (invert)
		MouseYDirection = -1.0f;
	else
		MouseYDirection = 1.0f;
}

ldx::scene::ISceneNodeAnimator * ldx::scene::CSceneNodeAnimatorCameraFPS::createClone(ISceneNode * node, CSceneManager * newManager)
{
	CSceneNodeAnimatorCameraFPS * newAnimator =
		new CSceneNodeAnimatorCameraFPS(CursorControl, RotateSpeed, MoveSpeed, JumpSpeed,
			0, 0, NoVerticalMovement);
	newAnimator->setKeyMap(KeyMap);
	return newAnimator;
}

void ldx::scene::CSceneNodeAnimatorCameraFPS::allKeysUp()
{
	for (mt::uint i = 0; i<EKA_COUNT; ++i)
		CursorKeys[i] = false;
}





